﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using DotNetCommon.Extensions;

namespace DBUtil.Builders;

public class SaveOneBuilder<T> : BaseBuilder where T : class, new()
{
    private T ent;
    private EntityInfo EntityInfo { get; set; }
    public TableName TableName { get; set; }

    internal SaveOneBuilder(DBAccess db, T ent) : base(db)
    {
        this.ent = ent ?? throw new Exception($"要保存的数据不能为空!");
        EntityInfo = db.GetEntityInfoInternal<T>();
        if (EntityInfo.PrimaryKeyColumn == null) throw new Exception($"class {EntityInfo.TypeClassFullName} 没有配置主键, 不能用于 SaveOneBuilder!");
        this.TableName = EntityInfo.TableName.CloneTableName();
    }
    internal SaveOneBuilder(DBAccess db) : base(db)
    {
        EntityInfo = db.GetEntityInfoInternal<T>();
        if (EntityInfo.PrimaryKeyColumn == null) throw new Exception($"class {EntityInfo.TypeClassFullName} 没有配置主键, 不能用于 SaveOneBuilder!");
        this.TableName = EntityInfo.TableName.CloneTableName();
    }

    public SaveOneBuilder<T> SetEntity(T entity)
    {
        this.ent = entity ?? throw new Exception($"要保存的数据不能为空!");
        return this;
    }

    #region AsTable
    public virtual SaveOneBuilder<T> AsTable(Func<string, string> func)
    {
        var newTableName = func?.Invoke(TableName.Name);
        if (newTableName.IsNotNullOrWhiteSpace()) TableName.SetName(newTableName);
        return this;
    }

    public virtual SaveOneBuilder<T> AsTableIf(bool condition, Func<string, string> func)
        => condition ? AsTable(func) : this;
    public virtual SaveOneBuilder<T> AsTable(Action<TableName> func)
    {
        func?.Invoke(TableName);
        return this;
    }

    public virtual SaveOneBuilder<T> AsTableIf(bool condition, Action<TableName> func)
        => condition ? AsTable(func) : this;
    #endregion

    internal readonly List<SetIgnoreItem> insertSetIgnores = [];
    internal readonly List<SetIgnoreItem> updateSetIgnores = [];
    #region Both SetColumn & IgnoreColumns
    private string[] ConvertColumnPureNames(params string[] names)
    {
        var arr = names.Select(name => EntityInfo.Props.FirstOrDefault(col => string.Equals(col.ColumnNamePure, name, StringComparison.OrdinalIgnoreCase) || string.Equals(col.ColumnNameQuoted, name))).ToList();
        var err = string.Empty;
        for (var i = 0; i < arr.Count; i++)
        {
            if (arr[i] == null) err += (err.Length > 0 ? "," : "") + $"【{names[i]}】";
        }
        if (err.Length != 0) throw new Exception($"类({typeof(T).GetClassFullName()})上不存在列({err})!");
        return arr.Select(i => i.ColumnNamePure).ToArray();
    }
    //SetColumn
    public SaveOneBuilder<T> SetColumn(string colname, object value)
    {
        SetIgnoreItem.SetColumn(insertSetIgnores, ConvertColumnPureNames(colname).FirstOrDefault(), value);
        SetIgnoreItem.SetColumn(updateSetIgnores, ConvertColumnPureNames(colname).FirstOrDefault(), value);
        return this;
    }
    public SaveOneBuilder<T> SetColumnIf(bool condition, string colname, object value) => condition ? SetColumn(colname, value) : this;
    public SaveOneBuilder<T> IgnoreColumns(params string[] colNames)
    {
        SetIgnoreItem.IgnoreColumns(insertSetIgnores, ConvertColumnPureNames(colNames));
        SetIgnoreItem.IgnoreColumns(updateSetIgnores, ConvertColumnPureNames(colNames));
        return this;
    }
    public SaveOneBuilder<T> IgnoreColumnsIf(bool condition, params string[] colNames) => condition ? IgnoreColumns(colNames) : this;
    public SaveOneBuilder<T> OnlyColumns(params string[] colNames)
    {
        SetIgnoreItem.OnlyColumns(insertSetIgnores, ConvertColumnPureNames(colNames));
        SetIgnoreItem.OnlyColumns(updateSetIgnores, ConvertColumnPureNames(colNames));
        return this;
    }
    public SaveOneBuilder<T> OnlyColumnsIf(bool condition, params string[] colNames) => condition ? OnlyColumns(colNames) : this;


    //expr
    public SaveOneBuilder<T> SetColumn(Expression<Func<T, object>> propSelector, object value)
    {
        SetIgnoreItem.SetColumn(insertSetIgnores, EntityInfo, propSelector, value);
        SetIgnoreItem.SetColumn(updateSetIgnores, EntityInfo, propSelector, value);
        return this;
    }
    public SaveOneBuilder<T> SetColumnIf(bool condition, Expression<Func<T, object>> propSelector, object value)
        => condition ? SetColumn(propSelector, value) : this;
    public SaveOneBuilder<T> SetColumn(Expression<Func<T, object>> propSelector, Expression<Func<object>> setExp) => SetColumn(propSelector, (object)setExp);
    public SaveOneBuilder<T> SetColumnIf(bool condition, Expression<Func<T, object>> propSelector, Expression<Func<object>> setExp)
       => condition ? SetColumn(propSelector, setExp) : this;
    public SaveOneBuilder<T> IgnoreColumns(Expression<Func<T, object>> propSelector)
    {
        SetIgnoreItem.IgnoreColumns(insertSetIgnores, EntityInfo, propSelector);
        SetIgnoreItem.IgnoreColumns(updateSetIgnores, EntityInfo, propSelector);
        return this;
    }
    public SaveOneBuilder<T> IgnoreColumnsIf(bool condition, Expression<Func<T, object>> propSelector)
        => condition ? IgnoreColumns(propSelector) : this;
    public SaveOneBuilder<T> OnlyColumns(Expression<Func<T, object>> propSelector)
    {
        SetIgnoreItem.OnlyColumns(insertSetIgnores, EntityInfo, propSelector);
        SetIgnoreItem.OnlyColumns(updateSetIgnores, EntityInfo, propSelector);
        return this;
    }
    public SaveOneBuilder<T> OnlyColumnsIf(bool condition, Expression<Func<T, object>> propSelector)
        => condition ? OnlyColumns(propSelector) : this;
    #endregion

    #region Insert SetColumn & IgnoreColumns
    //SetColumn
    public SaveOneBuilder<T> SetColumnForInsert(string colname, object value)
    {
        SetIgnoreItem.SetColumn(insertSetIgnores, ConvertColumnPureNames(colname).FirstOrDefault(), value);
        return this;
    }
    public SaveOneBuilder<T> SetColumnIfForInsert(bool condition, string colname, object value) => condition ? SetColumnForInsert(colname, value) : this;
    public SaveOneBuilder<T> IgnoreColumnsForInsert(params string[] colNames)
    {
        SetIgnoreItem.IgnoreColumns(insertSetIgnores, ConvertColumnPureNames(colNames));
        return this;
    }
    public SaveOneBuilder<T> IgnoreColumnsIfForInsert(bool condition, params string[] colNames) => condition ? IgnoreColumnsForInsert(colNames) : this;
    public SaveOneBuilder<T> OnlyColumnsForInsert(params string[] colNames)
    {
        SetIgnoreItem.OnlyColumns(insertSetIgnores, ConvertColumnPureNames(colNames));
        return this;
    }
    public SaveOneBuilder<T> OnlyColumnsIfForInsert(bool condition, params string[] colNames) => condition ? OnlyColumnsForInsert(colNames) : this;


    //expr
    public SaveOneBuilder<T> SetColumnForInsert(Expression<Func<T, object>> propSelector, object value)
    {
        SetIgnoreItem.SetColumn(insertSetIgnores, EntityInfo, propSelector, value);
        return this;
    }
    public SaveOneBuilder<T> SetColumnIfForInsert(bool condition, Expression<Func<T, object>> propSelector, object value)
        => condition ? SetColumnForInsert(propSelector, value) : this;
    public SaveOneBuilder<T> SetColumnForInsert(Expression<Func<T, object>> propSelector, Expression<Func<object>> setExp) => SetColumn(propSelector, (object)setExp);
    public SaveOneBuilder<T> SetColumnIfForInsert(bool condition, Expression<Func<T, object>> propSelector, Expression<Func<object>> setExp)
       => condition ? SetColumnForInsert(propSelector, setExp) : this;
    public SaveOneBuilder<T> IgnoreColumnsForInsert(Expression<Func<T, object>> propSelector)
    {
        SetIgnoreItem.IgnoreColumns(insertSetIgnores, EntityInfo, propSelector);
        return this;
    }
    public SaveOneBuilder<T> IgnoreColumnsIfForInsert(bool condition, Expression<Func<T, object>> propSelector)
        => condition ? IgnoreColumnsForInsert(propSelector) : this;
    public SaveOneBuilder<T> OnlyColumnsForInsert(Expression<Func<T, object>> propSelector)
    {
        SetIgnoreItem.OnlyColumns(insertSetIgnores, EntityInfo, propSelector);
        return this;
    }
    public SaveOneBuilder<T> OnlyColumnsIfForInsert(bool condition, Expression<Func<T, object>> propSelector)
        => condition ? OnlyColumnsForInsert(propSelector) : this;
    #endregion

    #region Update SetColumn & IgnoreColumns
    //SetColumn
    public SaveOneBuilder<T> SetColumnForUpdate(string colName, object value)
    {
        SetIgnoreItem.SetColumn(updateSetIgnores, ConvertColumnPureNames([colName]).FirstOrDefault(), value);
        return this;
    }
    public SaveOneBuilder<T> SetColumnIfForUpdate(bool condition, string colname, object value) => condition ? SetColumnForUpdate(colname, value) : this;
    public SaveOneBuilder<T> IgnoreColumnsForUpdate(params string[] colNames)
    {
        SetIgnoreItem.IgnoreColumns(updateSetIgnores, ConvertColumnPureNames(colNames));
        return this;
    }
    public SaveOneBuilder<T> IgnoreColumnsIfForUpdate(bool condition, params string[] colNames) => condition ? IgnoreColumnsForUpdate(colNames) : this;
    public SaveOneBuilder<T> OnlyColumnsForUpdate(params string[] colNames)
    {
        SetIgnoreItem.OnlyColumns(updateSetIgnores, ConvertColumnPureNames(colNames));
        return this;
    }
    public SaveOneBuilder<T> OnlyColumnsIfForUpdate(bool condition, params string[] colNames) => condition ? OnlyColumnsForUpdate(colNames) : this;


    //expr
    public SaveOneBuilder<T> SetColumnForUpdate(Expression<Func<T, object>> propSelector, object value)
    {
        SetIgnoreItem.SetColumn(updateSetIgnores, EntityInfo, propSelector, value);
        return this;
    }
    public SaveOneBuilder<T> SetColumnIfForUpdate(bool condition, Expression<Func<T, object>> propSelector, object value)
        => condition ? SetColumnForUpdate(propSelector, value) : this;
    public SaveOneBuilder<T> SetColumnForUpdate(Expression<Func<T, object>> propSelector, Expression<Func<object>> setExp) => SetColumn(propSelector, (object)setExp);
    public SaveOneBuilder<T> SetColumnIfForUpdate(bool condition, Expression<Func<T, object>> propSelector, Expression<Func<object>> setExp)
       => condition ? SetColumnForUpdate(propSelector, setExp) : this;
    public SaveOneBuilder<T> IgnoreColumnsForUpdate(Expression<Func<T, object>> propSelector)
    {
        SetIgnoreItem.IgnoreColumns(updateSetIgnores, EntityInfo, propSelector);
        return this;
    }
    public SaveOneBuilder<T> IgnoreColumnsIfForUpdate(bool condition, Expression<Func<T, object>> propSelector)
        => condition ? IgnoreColumnsForUpdate(propSelector) : this;
    public SaveOneBuilder<T> OnlyColumnsForUpdate(Expression<Func<T, object>> propSelector)
    {
        SetIgnoreItem.OnlyColumns(updateSetIgnores, EntityInfo, propSelector);
        return this;
    }
    public SaveOneBuilder<T> OnlyColumnsIfForUpdate(bool condition, Expression<Func<T, object>> propSelector)
        => condition ? OnlyColumnsForUpdate(propSelector) : this;
    #endregion

    public string ToSql()
    {
        var sqlInsert = db.Insert<T>().AsTable(TableName.FullNameQuoted).InsertIdentity().SetEntity(ent).ReplaceSetIgnores(insertSetIgnores).ToSql().TrimEnd(';');
        var sqlUpdate = db.Update<T>().SetEntity(ent).ReplaceSetIgnores(updateSetIgnores).ToSqlOnlySetSeg();
        return $"{sqlInsert} on duplicate key update {sqlUpdate};";
    }

    public int ExecuteAffrows()
    {
        var sql = ToSql();
        return RunWriteMonitor(new AfterWriteArgument
        {
            TableName = TableName.FullNameQuoted,
            WriteType = EnumWriteType.SaveOne
        }, () => db.ExecuteSql(sql, CommandType.Text, TimeoutSeconds));
    }

    public async Task<int> ExecuteAffrowsAsync(CancellationToken cancellationToken = default)
    {
        var sql = ToSql();
        return await RunWriteMonitorAsync(new AfterWriteArgument
        {
            TableName = TableName.FullNameQuoted,
            WriteType = EnumWriteType.SaveOne
        }, async () => await db.ExecuteSqlAsync(sql, CommandType.Text, TimeoutSeconds, null, cancellationToken));
    }
}
